using System;
using System.ComponentModel;
using System.Runtime.Serialization;

namespace Ns.Data.Entity
{
    /// <summary>
    /// Provides change tracking support to an existing type.
    /// </summary>
    /// <typeparam name="T">The type requiring change tracking support.</typeparam>
#if !SILVERLIGHT
    [Serializable]
#endif
    [DataContract(Name = "TrackableOf{0}")]
    public sealed class Trackable<T>
        : IPersistEntity
    {
        private const string PersistChangePropertyName = "PersistChange";
        private T _value;

        /// <summary>
        /// Gets the value of the current <see cref="Trackable&lt;T&gt;"/>.
        /// </summary>
        /// <value>The value of the current <see cref="Trackable&lt;T&gt;"/> object.</value>
        [DataMember(EmitDefaultValue = false)]
        public T Value
        {
            get { return _value; }
            internal set { _value = value; }
        }

        private PersistChange _persistChange;

        /// <summary>
        /// Gets or sets the persist change.
        /// </summary>
        /// <value>The persist change.</value>
        [DataMember(EmitDefaultValue = false)]
        public PersistChange PersistChange
        {
            get { return _persistChange; }
            set
            {
                if (_persistChange != value)
                {
                    _persistChange = value;
                    OnPropertyChanged(PersistChangePropertyName);
                }
            }
        }

        #region Constructors

        private Trackable()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Trackable&lt;T&gt;"/> class 
        /// with the <see cref="PersistEntity.CurrentPersistChange"/> 
        /// and according to the <see cref="PersistEntity.CurrentChangeTracking"/>.
        /// </summary>        
        /// <param name="value">The value.</param>
        /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
        public Trackable(T value)
            : this(value
            , PersistEntity.CurrentPersistChange
            ?? ((PersistEntity.CurrentChangeTracking == ChangeTracking.Enabled)
                ? PersistChange.Insert
                : PersistChange.None))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Trackable&lt;T&gt;"/> class
        /// with the specified <see cref="PersistChange"/>.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="persistChange">The persist change.</param>
        /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
        public Trackable(T value, PersistChange persistChange)
        {
            if (value == null)
                throw new ArgumentNullException("value");
            _value = value;
            _persistChange = persistChange;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Trackable&lt;T&gt;"/> class
        /// copied from the specified <paramref name="obj"/>.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <exception cref="ArgumentNullException"><paramref name="obj"/> is null.</exception>
        public Trackable(Trackable<T> obj)
        {
            if (obj == null)
                throw new ArgumentNullException("obj");
            this._value = obj._value;
            this._persistChange = obj._persistChange;
        }

        #endregion

        #region Overriding Members

        /// <summary>
        /// Converts the value of the current <see cref="Trackable&lt;T&gt;"/> object to its equivalent string representation. 
        /// </summary>
        /// <returns>A string representation of the value of the current <see cref="Trackable&lt;T&gt;"/> object.</returns>        
        public override string ToString()
        {
            return string.Format("PersistChange = {0}, Value = {1}",
                PersistChange,
                (Value != null)
                    ? Value.ToString()
                    : string.Empty);
        }

        #endregion

        #region Implicit/Explicit Operators

        /// <summary>
        /// Creates a new <see cref="Trackable&lt;T&gt;"/> object initialized to a specified value.         
        /// </summary>
        /// <param name="value">A value.</param>
        /// <returns>A <see cref="Trackable&lt;T&gt;"/> object whose <see cref="Value"/> property is initialized with the <c>value</c> parameter.</returns>
        public static explicit operator Trackable<T>(T value)
        {
            return new Trackable<T>(value);
        }

        /// <summary>
        /// Returns the value of a specified <see cref="Trackable&lt;T&gt;"/> object.
        /// </summary>
        /// <param name="value">A <see cref="Trackable&lt;T&gt;"/> instance.</param>
        /// <returns>The value of the <see cref="Value"/> property for the <c>value</c> parameter.</returns>
        public static implicit operator T(Trackable<T> value)
        {
            return (value != null)
                ? value.Value
                : default(T);
        }


        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs after the value of a property is changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the <see cref="E:PersistEntity.PropertyChanged"/> event.
        /// </summary>
        /// <param name="propertyName">The name of the property whose value has changed.</param>
        void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Raises the <see cref="E:PersistEntity.PropertyChanged"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.ComponentModel.PropertyChangedEventArgs"/> that contains the event data.</param>
        void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            //keep handler to avoid race condition
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, e);
        }

        #endregion
    }
}